﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace LiveCodeServer
{
    public class ClientHandler
    {
        #region Class Variables
        #region Constants
        #region Port Constants
        private const int I_NUM_QUICK_SEND_PORTS = 2;
        private const int I_NUM_QUICK_RECIEVE_PORTS = 2;
        private const int I_NUM_SLOW_SEND_PORTS = 1;
        private const int I_NUM_SLOW_RECIEVE_PORTS = 1;

        private const int I_CLIENT_SEND_PORT_START = 8251;
        private const int I_CLIENT_RECIEVE_PORT_START = 8301;
        #endregion

        #region File Path Contants
        public const string S_USERS_FILE_PATH = "LiveCodeUsers.xml"; //@"..\..\Test XML\LiveCodeUsers.xml";
        public const string S_PROJECTS_FILE_PATH = "LiveCodeProjects.xml"; //@"..\..\Test XML\LiveCodeProjects.xml";
        #endregion
        #endregion

        private ServerForm form;

        private IOHandler ioHandler;
        private List<Client> lstClients = new List<Client>();
        private List<User> lstUsers = new List<User>();

        private int iCurrentClientSendPort = 0;

        //TODO Take out this port and logic
        //NOT NEEDED IN FINAL VERSION, ONLY FOR RUNNING MULTIPLE INSTANCES ON SAME MACHINE
        private int iCurrentClientRecievePort = 0;
        #endregion

        public ClientHandler(ServerForm _form)
        {
            try
            {
                this.form = _form;
                this.ioHandler = new IOHandler(new AsyncCallback(OnClientConnect));
            }
            catch (Exception e)
            {
                if (this.form != null)
                {
                    //TODO handle better

                    string message = "Method: ClientHandler.ClientHandler";
                    message += "\nException: " + e.Message;

                    this.AddMessage(message, System.Drawing.Color.Red);
                }
            }
        }

        #region Get Client Ports
        public List<int> GetNewQuickSendPorts()
        {
            try
            {
                //TODO needs to resuse ports if possible
                if (this.iCurrentClientSendPort == 0)
                {
                    this.iCurrentClientSendPort = I_CLIENT_SEND_PORT_START;
                }

                List<int> lstQuickSendPorts = new List<int>();
                for (int i = 0; i < I_NUM_QUICK_SEND_PORTS; i++)
                {
                    lstQuickSendPorts.Add(this.iCurrentClientSendPort++);
                }

                return lstQuickSendPorts;
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: ClientHandler.GetNewQuickSendPorts";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }

            return null;
        }

        public List<int> GetNewQuickRecievePorts()
        {
            try
            {
                //TODO Change back to use commented out logic instead of below IF
                //int iClientRecievePort = I_CLIENT_RECIEVE_PORT_START;
                if (this.iCurrentClientRecievePort == 0)
                {
                    this.iCurrentClientRecievePort = I_CLIENT_RECIEVE_PORT_START;
                }


                List<int> lstQuickRecievePorts = new List<int>();
                for (int i = 0; i < I_NUM_QUICK_RECIEVE_PORTS; i++)
                {
                    lstQuickRecievePorts.Add(iCurrentClientRecievePort++);
                }

                return lstQuickRecievePorts;
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: ClientHandler.GetNewQuickRecievePorts";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }

            return null;
        }

        public List<int> GetNewSlowSendPorts()
        {
            try
            {
                if (this.iCurrentClientSendPort == 0)
                {
                    this.iCurrentClientSendPort = I_CLIENT_SEND_PORT_START;
                }

                List<int> lstQuickSendPorts = new List<int>();
                for (int i = 0; i < I_NUM_SLOW_SEND_PORTS; i++)
                {
                    lstQuickSendPorts.Add(this.iCurrentClientSendPort++);
                }

                return lstQuickSendPorts;
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: ClientHandler.GetNewSlowSendPorts";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }

            return null;
        }

        public List<int> GetNewSlowRecievePorts()
        {
            try
            {
                //TODO Change back to use commented out logic instead of below IF
                //Slow recieve ports start after the quick recieve ports
                //int iClientRecievePort = I_CLIENT_RECIEVE_PORT_START + I_NUM_QUICK_RECIEVE_PORTS;
                if (this.iCurrentClientRecievePort == 0)
                {
                    this.iCurrentClientRecievePort = I_CLIENT_RECIEVE_PORT_START;
                }

                List<int> lstSlowRecievePorts = new List<int>();
                for (int i = 0; i < I_NUM_SLOW_RECIEVE_PORTS; i++)
                {
                    lstSlowRecievePorts.Add(iCurrentClientRecievePort++);
                }

                return lstSlowRecievePorts;
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: ClientHandler.GetNewSlowRecievePorts";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }

            return null;
        }
        #endregion

        /// <summary>
        /// Begins listening for incomming connections
        /// </summary>
        public void GoOnline()
        {
            try
            {
                this.ioHandler.StartListening();
            }
            catch (Exception e)
            {
                System.Windows.Forms.MessageBox.Show("Unable to go online");

                string message = "Method: ClientHandler.GoOnline";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        /// <summary>
        /// Disconnects all clients from the server and removes them from the list
        /// </summary>
        public void GoOffline()
        {
            try
            {
                //Disconnect the default port first so no new incomming connections go through
                this.ioHandler.Disconnect();

                int iClientCount = this.lstClients.Count;
                for (int i = 0; i < iClientCount; i++)
                {
                    //Always pull from the top of the list because "DisconnectFromClient" will remove the client from the list
                    Client client = this.lstClients[0];
                    client.DisconnectFromClient();
                }
            }
            catch (Exception e)
            {
                string message = "Method: ClientHandler.GoOffline";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        public void UserLoggedIn(User clientUser)
        {
            try
            {
                this.lstUsers.Add(clientUser);
                this.form.UpdateUsersConnected(this.lstUsers.Count);
                this.form.UserConnected(clientUser.ID, clientUser.Name);
            }
            catch (Exception e)
            {
                string message = "Method: ClientHandler.UserLoggedIn";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        /// <summary>
        /// Updates server form with new user count, disconnects all client sockets
        /// </summary>
        /// <param name="client"></param>
        public void UserLoggedOut(User clientUser)
        {
            try
            {
                //Send a user logged out message to all users connected to the same project
                Project project = clientUser.ConnectedProject;
                foreach (User user in project.OnlineUsers)
                {
                    if (user.ID != clientUser.ID)
                    {
                        user.Client.SendChatUserLogoff(clientUser.ID);
                    }
                }

                //Remove the user from the project
                clientUser.ConnectedProject.OnlineUsers.Remove(clientUser);

                //If the client hasn't been removed from the list of clients
                if (this.lstClients.Contains(clientUser.Client))
                {
                    //Remove client from list of clients
                    this.lstClients.Remove(clientUser.Client);

                    //Make sure all client sockets are disconnected
                    clientUser.Client.DisconnectFromClient();

                    //Update the user count on the form
                    if (this.lstUsers.Contains(clientUser))
                    {
                        this.lstUsers.Remove(clientUser);
                        this.form.UserDisconnected(clientUser.ID);
                    }

                    this.form.UpdateUsersConnected(this.lstUsers.Count);
                }
            }
            catch (Exception e)
            {
                string message = "Method: ClientHandler.UserLoggedOut";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        public void ProjectLogin(int iUserID, int iProjectiD)
        {
            try
            {
                this.form.ProjectLogin(iUserID, iProjectiD);
            }
            catch (Exception e)
            {
                string message = "Method: ClientHandler.ProjectLogin";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        public void ChatLogin(int iUserID, int iProjectID)
        {
            if (this.lstUsers.Where(u => u.ID == iUserID).ToList().Count() == 1)
            {
                User newUser = this.lstUsers.Where(u => u.ID == iUserID).ToList()[0];
                Project project = newUser.ConnectedProject;
                //Send a chat logon message to users logged onto project
                foreach (User user in project.OnlineUsers)
                {
                    if (user.ID != newUser.ID)
                    {
                        user.Client.SendChatUserLogon(newUser.ID, newUser.Name);

                        newUser.Client.SendChatUserLogon(user.ID, user.Name);
                    }
                }
            }
        }

        /// <summary>
        /// Finds a project from the given project id
        /// </summary>
        /// <param name="iProjectID"></param>
        /// <returns>Returns the project object if it exists, null if it does not</returns>
        public Project GetProject(int iProjectID)
        {
            try
            {
                List<User> projectUsers = this.lstUsers.Where(u => u.ConnectedProject != null && u.ConnectedProject.ID == iProjectID).ToList();
                if (projectUsers.Count > 0)
                {
                    return projectUsers[0].ConnectedProject;
                }
            }
            catch (Exception e)
            {
                string message = "Method: ClientHandler.GetProject";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }

            return null;
        }

        /// <summary>
        /// Adds a message to the "message" tab of the server ui
        /// </summary>
        /// <param name="sMessage"></param>
        /// <param name="color"></param>
        public void AddMessage(string sMessage, System.Drawing.Color color)
        {
            try
            {
                this.form.AddMessage(sMessage, color);
            }
            catch (Exception e)
            {
                string message = "Method: ClientHandler.AddMessage";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        /// <summary>
        /// Sends a message to a user
        /// </summary>
        /// <param name="iToID"></param>
        /// <param name="iFromID"></param>
        /// <param name="sText"></param>
        public void SendChatMessage(int iToID, int iFromID, string sText)
        {
            //Check to see if the user is logged in
            if (this.lstUsers.Where(u => u.ID == iToID).ToList().Count == 1)
            {
                User toUser = this.lstUsers.Where(u => u.ID == iToID).ToList()[0];
                toUser.Client.SendChatMessage(iToID, iFromID, sText);
            }
        }

        #region Events
        /// <summary>
        /// When a client connects create a new client class with the socket
        /// </summary>
        /// <param name="asyn"></param>
        private void OnClientConnect(IAsyncResult asyn)
        {
            try
            {
                Socket socClient = this.ioHandler.Listener.EndAccept(asyn);

                //Create a new client connection with the socket found
                Client newClient = new Client(socClient, this);
                this.lstClients.Add(newClient);
            }
            catch (SocketException socExp)
            {
                string message = "Method: ClientHandler.OnClientConnect";
                message += "\nException: " + socExp.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }
            catch (Exception e)
            {
                string message = "Method: ClientHandler.OnClientConnect";
                message += "\nException: " + e.Message;

                this.AddMessage(message, System.Drawing.Color.Red);
            }

            this.ioHandler.StartListening();
        }
        #endregion
    }
}
